-- DungeonGambleM
-- Created by cheny3 Nov/01/2016
-- 赌博模块

module("DungeonGambleM", package.seeall);

-- 最大胜率
local MAX_WIN_RATIO = 100;

-- 配置表
local gambles = {};
local gambleOptions = {};

-- 内部函数
local whenPlayerMove;
local whenLeaveDungeon;
local whenNextFloor;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    gambles = {};
    gambleOptions = {};

    local gambles_CSV = get_gamble_CSV();
    for i = 1, #gambles_CSV do
        local row = gambles_CSV[i];

        gambles[row.id] = row;
    end

    local gamble_option_CSV = get_gamble_option_CSV();
    for i = 1, #gamble_option_CSV do
        local row = gamble_option_CSV[i];

        gambleOptions[row.id] = row;
    end

    gambles_CSV = nil;
    gamble_option_CSV = nil;
end

-- 初始化
function init()
    -- 载入配置表
    loadCsv();

    -- 移除所有关注
    EventMgr.removeAll("DungeonGambleM");

    -- 关注玩家离开地牢的事件
    EventMgr.register("DungeonGambleM", event.LEAVE_DUNGEON, function()
        whenLeaveDungeon();
    end);

    -- 关注进入下一层的事件
    EventMgr.register("DungeonGambleM", event.NEXT_FLOOR, function()
        whenNextFloor();
    end);

    -- 关注层数回退（大地之门等技能）的事件
    EventMgr.register("DungeonGambleM", event.BACK_FLOOR, function()
        whenNextFloor();
    end);
end

-- 关注玩家离开地牢的事件
function whenLeaveDungeon()
    -- 清除回合数
    if ME.user.dbase:query("gamble_info") then
        ME.user.dbase:delete("gamble_info");
    end
end

-- 进入下一层的回调
function whenNextFloor()
    -- 清除回合数
    if ME.user.dbase:query("gamble_info") then
        ME.user.dbase:delete("gamble_info");
    end
end

-- 查询赌桌信息
function query(id, path)
    local m = gambles[id];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 查询赌注信息
function queryOption(id, path)
    local m = gambleOptions[id];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 计算赌桌的奖励
function calcGridBonus(pos)
    local ret = {};

    local grid = DungeonM.getGridByPos(pos);
    if not grid or grid.type ~= GRID_TYPE_NORMAL_GAMBLE or
        grid.state == GRID_STATE_DISABLE then
        trace("DungeonGambleM", "不是赌桌或已失效。");
        return ret;
    end

    local gambleId = grid.gamble_id;
    local configBonus = query(gambleId, "bonus");

    local prefix = "formula_";
    if type(configBonus) == "string" and string.startWith(configBonus, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(configBonus, string.len(prefix) + 1);
        configBonus = FormulaM.invoke(formulaName, ME.user);
    end

    local bonusList = table.deepcopy(configBonus) or {};
    -- 如果只有一种奖励，需要转换一下格式
    if type(bonusList[1]) == 'number' then
        bonusList = { bonusList };
    end

    return bonusList;
end

-- 获取赌注胜率
function getGambleWinRatio(optionId)
    return tonumber(queryOption(optionId, "success_rate"));
end

-- 获取获胜奖励
function calcGambleWinBonus(optionId)
    local configBonus = queryOption(optionId, "bonus");

    local prefix = "formula_";
    if type(configBonus) == "string" and string.startWith(configBonus, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(configBonus, string.len(prefix) + 1);
        configBonus = FormulaM.invoke(formulaName, ME.user);
    end

    local bonusList = table.deepcopy(configBonus) or {};
    -- 如果只有一种奖励，需要转换一下格式
    if type(bonusList[1]) == 'number' then
        bonusList = { bonusList };
    end

    return bonusList;
end

-- 计算赌博结果
function calcGambleResult(optionId)
    -- 计算胜率
    local winRatio = getGambleWinRatio(optionId);
    if winRatio < 0 then
        error("选项胜率小于0");
    end

    local seed = DungeonM.getRandSeed("gamble");
    local randValue = seed % MAX_WIN_RATIO;
    local winRatio  = winRatio % MAX_WIN_RATIO;

    -- 迷宫小关一定赢
    local dungeonId = DungeonM.getDungeonId();
    if type(dungeonId) == "number" and dungeonId > 0 and
       DungeonAreaM.query(dungeonId, "type") == "child" then
        return 1;
    end

    --塔罗牌冈布奥一定赢
    local classId = ME.user.dbase:query("active_pet", 0);
    if classId == 29 then
        return 1;
    end

    if randValue <= winRatio then
        return 1;
    end

    return 0;
end

-- 获取赌博记录
function getGambleRecord()
    local gambleRecord = ME.user.dbase:query("gamble_info");
    if not gambleRecord then
        -- 未赌过
        return nil;
    end

    -- 赌过了，返回结果
    return gambleRecord.result;
end

-- 获取赌桌所有的赌注
function getGambleOptions(gambleId)
    local ret = query(gambleId, "options");
    table.sort(ret);
    return ret;
end

-- 计算选项的消耗
function calcGambleOptionCost(optionId)
    local configCost = queryOption(optionId, "cost");

    local prefix = "formula_";
    if type(configCost) == "string" and string.startWith(configCost, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(configCost, string.len(prefix) + 1);
        configCost = FormulaM.invoke(formulaName, ME.user);
    end

    local costList = table.deepcopy(configCost) or {};
    -- 如果只有一种奖励，需要转换一下格式
    if type(costList[1]) == 'number' then
        costList = { costList };
    end

    return costList;
end

-- 判断是否允许赌博
function canDoGamble(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid or grid.type ~= GRID_TYPE_NORMAL_GAMBLE or
        grid.state == GRID_STATE_DISABLE then
        trace("DungeonGambleM", "不是赌桌或已失效。");
        return false;
    end

    if DungeonM.isAllMonsterDead() then
        trace("DungeonGambleM", "怪物已经全清了，不允许赌博。");
        return false;
    end

    if getGambleRecord() then
        trace("DungeonGambleM", "已经赌博过了，不允许赌博。");
        return false;
    end

    return true;
end

-- 计算是否足够扣除
function isEnoughToCost(costList)
    -- 扣除消耗
    for _, cost in ipairs(costList) do
        if cost[1] == 1 then
            if ItemM.getAmount(ME.user, cost[2]) < cost[3] then
                trace("DungeonGambleM", "赌桌需求物品%d不足，需求：%d\n", cost[2], cost[3]);
                return false;
            end
        elseif cost[1] == 2 then
            if ME.user:queryAttrib(cost[2]) < cost[3] then
                trace("DungeonGambleM", "赌桌需求属性%s不足，需求：%d\n", cost[2], cost[3]);
                return false;
            end
        end
    end

    return true;
end

-- 赌博
function doGamble(pos, optionId)
    if not canDoGamble(pos) then
        trace("DungeonGambleM", "当前(%d)不允许赌博。", pos);
        return false;
    end

    -- 格子
    local grid = DungeonM.getGridByPos(pos);

    -- 赌桌的ID
    local gambleId = grid.gamble_id;

    -- 赌桌的合法选项
    local options = getGambleOptions(gambleId)
    if table.indexOf(options, optionId) < 0 then
        trace("DungeonGambleM", "赌桌%d，不存在赌注%d", gambleId, optionId);
        return false;
    end

    -- 计算扣除消耗
    local allCost = calcGambleOptionCost(optionId);

    -- 消耗是否足够
    if not isEnoughToCost(allCost) then
        return false;
    end

    -- 计算赌博结果
    local result = calcGambleResult(optionId);

    -- 记录赌博结果
    local gambleInfo = ME.user.dbase:query("gamble_info", {});
    gambleInfo = { ["option_id"] = optionId, ["result"] = result };
    ME.user.dbase:set("gamble_info", gambleInfo);

    local bonusList = {};
    if result == 1 then
        -- 赢了
        trace("DungeonTentM", "赌博结果：赢了。");

        -- 计算获胜奖励
        bonusList = calcGambleWinBonus(optionId);
        for _, bonus in ipairs(bonusList) do
            BonusM.doBonus(bonus, "gamble_bonus");
        end
    else
        -- 输了
        trace("DungeonTentM", "赌博结果：输了。");

        -- 扣除消耗
        for _, cost in ipairs(allCost) do
            if cost[1] == 1 then
                if not ItemM.costAmount(ME.user, cost[2], cost[3]) then
                    trace("DungeonGambleM", "赌桌扣除物品%d失败，需求：%d\n", cost[2], cost[3]);
                    return false;
                end
            elseif cost[1] == 2 then
                if not ME.user:costAttrib(cost[2], cost[3]) then
                    trace("DungeonGambleM", "赌桌扣除属性%s失败，需求：%d\n", cost[2], cost[3]);
                    return false;
                end
            end
        end
    end

    -- 抛出赌博事件
    EventMgr.fire(event.NORMAL_GAMBLE, { ["result"] = result, ["gambleId"] = gambleId,
        ["optionId"] = optionId, ["bonus"] = bonusList, ["pos"] = pos, });

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, grid:getPos());
    end

    return true;
end